// escape from zurg (http://lambda-the-ultimate.org/node/2434)

object main extends Solvers with Application {
  sealed abstract class Toy(val time: Int)
    case object Buzz  extends Toy(5)
    case object Woody extends Toy(10)
    case object Rex   extends Toy(20)
    case object Hamm  extends Toy(25)

  case class ToyState(TODO: List[Toy], elapsedMinutes: Int) extends State {
    def done = TODO.isEmpty && elapsedMinutes <= 60

    def explore = (for(
      (notMoved, moved) <- TODO.dropAny; // TODO: hint maximize(_.time)
      backAndForth <- notMoved.pickAny
    ) yield ToyState(notMoved, elapsedMinutes + Iterable.max(List(backAndForth, moved) map (_.time)) + backAndForth.time))
  }

  println(solve(List((ToyState(List(Buzz, Woody, Rex, Hamm), 0))).elements))
}

trait Solvers {
  // Idiom? Monad? 
  type Solver[+T] = Iterator[T]
  
/*  class Solver[+S <: State](val start: S) extends Iterator[S]{
    def solve: Option[S] = find(_.done)
    
    def next = start.explore.next
    def hasNext = start.done
    // map, flatMap, filter
    // propagate & distribute?
  }*/

  def solve(xs: Iterator[State]) = {
    var curr : Iterator[State] = xs
    
    while(curr.find(_.done) isEmpty) {
      curr = curr flatMap {_.explore}
    }

    curr.find(_.done)
  }

  trait State {
    def done: Boolean
    def explore: Solver[State]
  }

  implicit def makeSolvingList[T](xs: List[T]) = new SolvingList[T](xs)
  class SolvingList[T](val self: List[T]) {
    def dropAny: Solver[(List[T], T)] // with Hintable[T]
       = new Iterator[(List[T], T)] {
         var skipped: List[T] = List() 
         var curr = self
         def next = {
           val hd = curr.head
           skipped = hd :: skipped
           curr = curr.tail
           (skipped ::: curr, hd)
         }
         def hasNext = !curr.isEmpty
        }
      
    def pickAny: Solver[T]  // with Hintable[T]
       = new Iterator[T] {
         var curr = self
         def next = {
           val hd = curr.head
           curr = curr.tail
           hd
         }
         def hasNext = !curr.isEmpty
        }
  }
}